/* implementacao do client.c */
#include "lib/client.h"
#include "webserver.h"

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

#include "lib/pio.h"

enum {
  CLIENT_READY = 0,
  CLIENT_READ_HEADER,
  CLIENT_GET,
  CLIENT_PUT,
  
  CLIENT_QUIT,
  CLIENT_MAX_STATE
};

const char * header_400 = "HTTP/1.0 400 Bad Food\r\n\r\n";
const size_t header_400_len = 25;

const char * header_404 = "HTTP/1.0 404 Cannot find %s here.\r\n\r\nNot Found;";
const size_t header_404_len = 33+11;

const char * header_200 = "HTTP/1.0 200 Got it!\r\n"
                          "Connection: close\r\n"
                          "\r\n";
const size_t header_200_len = 22+19+2;

const char * header_201 = "HTTP/1.0 201 Will do.\r\n"
                          "Connection: close\r\n"
                          "\r\n";
const size_t header_201_len = 22+20+2;

const char * header_500 = "HTTP/1.0 500 Bad Server :/\r\n\r\n";
const size_t header_500_len = 30;

enum iostate {
  IO_READING = -1,
  IO_WRITING = -2,
  IO_IDLE = 0,
  IO_READ_READY = 1,
  IO_WRITE_READY = 2
};

/* um cliente faz soh uma requisicao.
 * Ainda bem */
typedef struct clientdata_t
{
  int state;
  
  enum iostate ioready; /* estado de io: < 0 trabalhando, > 0 pronto, 0 IDLE */
  
  char * filename;
  FILE * resource;

} clientdata;

/* preenche um buffer usando um arquivo
 * retorna quantos bytes conseguiu encher
 * ou negativo em caso de erro */
int fill(void * to, FILE * from, size_t max);

int dump(void * from, FILE * to, size_t max);

/* callback de leitura pronta */
void server_threaded_ready(void * data, size_t total);

/* update dos clientes, substitui o update default
 * do servidor. */
int server_threaded_update(client * c)
{
  
  char *in = 0, *out = 0; /* buffers */
  char *s = 0, *method = 0, *filename = 0, *path = 0;
  char *body = 0; /* comeco do body */
  clientdata * data;
  int len;
  
  if (c->data == 0)
    c->data = calloc(1, sizeof(clientdata));
  
  data = (clientdata *)c->data;
                 
  in = (char *)c->input;
  out = (char *)c->output;
  
  if (data->state == CLIENT_QUIT)
  return CLIENT_QUIT;
  
//   if (c->inputlen != 0)
//   {
    switch(data->state)
    {
      case CLIENT_READY: /* novo cliente, dados novos, pega header dele! */
        data->state = CLIENT_READ_HEADER;
        break;
        
      /* nao fazemos nada no caso de GET e PUT
       * as funcoes de GET e PUT sao
       * handleadas pelo update() do pio */        
      case CLIENT_GET:
        /* ja leu alguma coisa (IOREADY >= 0) e continua vazio. Sai */
//         printf("%s %d   lock mutex %x\n", __func__, __LINE__, &(c->lock));
        pthread_mutex_lock(&(c->lock));
        if (c->outputlen == 0 && data->ioready >= 0)
        {
          data->state = CLIENT_QUIT;
//           printf("%s %d unlock mutex %x\n", __func__, __LINE__, &(c->lock));
          pthread_mutex_unlock(&(c->lock));
          return 0;
        }
        
        /* buffers de saida pronto pra escrever, nao ta fazendo nada */
        if (c->outputlen == c->sent && data->ioready >= 0)
        {
          /* comando de leitura */
          data->ioready = IO_READING; /* seta estado como trabalhando */
          c->sent = c->outputlen = 0;
          pioread(io, data->resource, out, c->outputsz, c, server_threaded_ready);
//           printf("%s %d unlock mutex %x\n", __func__, __LINE__, &(c->lock));
          pthread_mutex_unlock(&(c->lock));
          return 0;
        }

        
//         printf("%s %d unlock mutex %x\n", __func__, __LINE__, &(c->lock));
        pthread_mutex_unlock(&(c->lock));
        return 0;
        break;
        
      case CLIENT_PUT:
        /* escrita pronta, buffer de leitura do servidor vazio =
         * = nenhum dado via rede, quit */
        if (c->inputlen == 0 && data->ioready >= 0)
        {
          data->state = CLIENT_QUIT;
          return 0;
        }
        
        /* buffer de entrada pronto para escrever no arquivo, e nao ta
         * esperando escrever nada */
        if (c->read == c->inputlen && data->ioready >= 0)
        {
          c->read = c->inputlen = 0;
          data->ioready = IO_WRITING;
          piowrite(io, data->resource, out, c->outputsz, c, server_threaded_ready);
        }
        
        return 0;
        break;

      default:
        break;
    }
//   }
  
  if (data->state == CLIENT_READ_HEADER)
  {
    if ((body = strstr(in, "\r\n\r\n")) == 0)
    {
      if ((c->inputlen == c->inputsz)) /* ja leu todo o buffer e nada */
        goto badfood;
      else
        return 0; /* espere a proxima leitura */
    } 
    else
    {
      body += 4; /* caminha os \r\n\r\n */
      
      /* marca a primeira linha */
      *strstr(in, "\r\n") = 0;
      s = in;
      method = strsep(&s, " ");
      
      if (strcmp(method, "GET") == 0)
      {
        data->state = CLIENT_GET;
      }
      else if(strcmp(method, "PUT") == 0)
      {
        data->state = CLIENT_PUT;
      }
      else
        goto badfood;

      filename = strsep(&s, " ");
      if (!filename)
        goto badfood;
      
      path = calloc(1, strlen(serverpath) + strlen(filename) + 2);
      
      if (filename[0] != '/')
        sprintf(path, "%s/%s", serverpath, filename);
      else
        sprintf(path, "%s%s", serverpath, filename);
      

      data->filename = realpath(path, NULL);
      if (data->filename == 0)
      {
        if (errno == ENOENT && data->state == CLIENT_GET) /* file not found */
          goto four0four;
        
        else if (data->state == CLIENT_PUT)
        {
          data->resource = fopen(path, "w+");
          data->filename = realpath(path, NULL);
          c->outputlen = header_201_len;
          strcpy(out, header_201);
        }
      }
      else
      {
        data->resource = fopen(data->filename, "r+");
        if (data->resource == 0)
          goto badserver;
          c->outputlen = header_200_len;
          strcpy(out, header_200);
      }

      if (data->filename == 0 || strstr(data->filename, serverpath) != data->filename)
        goto four0four;
      
      /* se tiver corpo, traz pro comeco do buffer */
      if ((c->inputlen - (body-in)) != 0)
      {
        /* move o body pro comeco */
        len = c->inputlen - (body-in);
        memmove(in, body, c->inputlen - (body-in));
        c->inputlen = len;
      }
      else
      {
        c->inputlen = 0;
      }
      
      if (path)
        free(path);
      
    }
    
    return 0;
    
    four0four:
    {
      c->outputlen = sprintf(out, header_404, filename) +1;
      printf("%s\n", out);
      data->state = CLIENT_QUIT;
      if (path)
        free(path);
      
      if (data->resource)
        fclose(data->resource);
      
      perror("errno");
      
      return 0;
    }
      
    badfood:
    {
      strcpy(out, header_400);
      printf("%s\n", out);
      c->outputlen = header_400_len;
      if (path)
        free(path);
      data->state = CLIENT_QUIT;
      return 0;
    }
    
    badserver:
    {
      strcpy(out, header_500);
      printf("%s\n", out);
      c->outputlen = header_500_len;
      data->state = CLIENT_QUIT;
      if (path)
        free(path);
      
      return 0;
    }
  }
  return 0;
}

int server_threaded_quit(client * clientp)
{
  clientdata * data = (clientdata*) clientp->data;
  if (data != 0)
  {
    if (data->filename)
      free(data->filename);
    
    if (data->resource)
      fclose(data->resource);
    
    free(clientp->data);
  }
  
  return 0;
}

void server_threaded_ready(void *c_, size_t total)
{
  /* io ready */
  client * c = c_;
  clientdata * data = c->data;
  
//   printf("%s@%d Data is here.\n", __func__, __LINE__);
  pthread_mutex_lock(&(c->lock));
  if (data->state == CLIENT_GET)
    c->outputlen = total;
  else if (data->state == CLIENT_PUT)
    c->inputlen = total;
  
//   printf("data->state = %d, data->ioready = %d, totals = %d\n", data->state, data->ioready, total);
  
  data->ioready = IO_IDLE;
//   printf("%s %d unlock mutex %x\n", __func__, __LINE__, &(c->lock));
  pthread_mutex_unlock(&(c->lock));
}
